<?php
/**
 *
 * 公共控制器基类
 *
 * @author     liang<lang@vip.deyi.com>
 * @since      2015年5月21日
 * @copyright  deyi.com
 */

namespace Apps\Common\Controller;

use Apps\Common\Constant\BusinessConstant;
use Apps\Common\Constant\CacheKeyConstant;
use Apps\Common\Constant\CacheTimeoutConstant;
use Apps\Common\Constant\ExceptionConstant;
use Apps\Common\Logger\FileLogger;
use Apps\Common\Model\BaseModel;
use Apps\Common\Service\ReqFreqControlService;
use Deyi\Functions\CacheFunction;
use Deyi\Functions\DateTimeFunction;
use Deyi\Functions\HttpFunction;
use Deyi\Functions\MobileDeviceDetect;
use Deyi\Functions\UtilFunction;
use Phalcon\Mvc\Controller;

/**
 * Class BaseController
 * @package Apps\Common\Controller
 */
abstract class BaseController extends Controller
{
    //论坛用户uid及token COOKIES名序列值
    const PRIVATE_PROTOCOL_PARAM = 'dy_p';

    // 静态资源时间戳
    public static $STATIC_RES_VERSION = '0.0.200';

    /*
     * 提交的 请求 json 转换为的 数组
     */
    protected $jsonReqArr = [];

    // 原生body通信数据
    protected $rawJsonBody;

    // 应用配置
    protected static $_config;

    /**
     * @var \Redis
     */
    protected static $_redis;

    /**
     * redis非序列化访问驱动
     * @var \Redis
     */
    protected static $_ns_redis;

    /**
     * @var \Memcached
     */
    protected static $_memcached;

    /**
     * @var \Phalcon\Cache\Backend\File
     */
    protected static $_fileCache;

    /**
     * @var FileLogger
     */
    protected static $_logger;

    /**
     * 单页数据量size设定
     * @var int
     */
    public static $PAGE_SIZE = 0;

    /**
     * @var 异常错误代码号
     */
    public static $THROW_ERR_CODE = 0;

    /**
     * 互斥锁缓存key
     * @var string
     */
    public static $_mutex_lock_cache_key = '';

    /**
     * 互斥锁缓存时间
     * @var int
     */
    public static $_mutex_lock_timeout = 0;


    /**
     * 动作名
     * @var string
     */
    protected static $reqActionName = '';

    /**
     * 控制器名
     * @var string
     */
    protected static $reqControllerName = '';

    protected static $_ticketCache = [];

    public function initialize()
    {

        $bodyJson = $this->request->hasFiles() ? '' : $this->request->getJsonRawBody(true); //提交的json

        if ($this->request->get('pagesize')) {
            self::$PAGE_SIZE = (int)$this->request->get('pagesize');
        }

        // 获取原始数据用于数据签名
        $this->rawJsonBody = $this->request->getRawBody();

        self::filterStr($bodyJson);
        $this->jsonReqArr = $bodyJson;

        //控制器相关
        static::$reqActionName = strtolower($this->dispatcher->getActionName());
        static::$reqControllerName = strtolower($this->dispatcher->getControllerName());

        // API接口不需要引入模板
        $this->view->disable();

        // 初始化缓存，配置数据
        if (is_null(self::$_config)) {
            // 设定参数配置
            self::$_config = $this->getDI()->get('config');
            // 注入redis，memcached
            self::$_redis = $this->getDI()->get('redis');
            self::$_memcached = $this->getDI()->get('memcached');
            // 注入日志
            self::$_logger = $this->getDI()->get('logger');
            // redis非序列化驱动
            self::$_ns_redis = $this->getDI()->get('redis_none_serializer');

            // 注入文件缓存
            self::$_fileCache = $this->getDI()->get('fileCache');


            $this->view->setVars(
                [
                    'static_res_version' => static::$STATIC_RES_VERSION
                ]
            );

        }
    }


    /**
     *  获取指定参数值,同时可指定默认参数值
     *
     * @author liang<lang@vip.deyi.com>
     *
     * @param            $fieldName
     * @param null $defaultValue
     * @param null $forceType
     * @param bool|false $get
     * @param string $error
     *
     * @return bool|float|mixed|null|string
     * @throws \Exception
     */
    protected function getParamValue($fieldName, $defaultValue = null, $forceType = null, $get = true, $error = '')
    {

        $value = isset($this->jsonReqArr[$fieldName]) ? $this->jsonReqArr[$fieldName] : null;

        if ($get && is_null($value)) {
            $value = isset($this->request->get($fieldName)[0]) ? $this->request->get($fieldName) : null;
        }

        //特殊的 闭包 处理方式
        if ($defaultValue instanceof \Closure) {
            $retValue = $defaultValue($value);
            if (isset($retValue)) {
                $value = $retValue;
            }
        } else {
            $value = null === $value ? $defaultValue : $value;
        }

        $filterValue = is_string($value) ? trim(UtilFunction::filterXSS($value)) : $value;

        if ($value !== 0 && $value !== '0' && (empty($value) || empty($filterValue)) && is_null($defaultValue)) {
            if (empty($error)) {
                throw new \Exception($fieldName . '参数为空', ExceptionConstant::PARAMS_EMPTY);
            } else {
                throw new \Exception($error, ExceptionConstant::PARAMS_EMPTY);
            }
        }

        if (is_null($forceType)) {
            return $filterValue;
        }

        switch ($forceType) {
            case 'int':
                return intval($filterValue);
            case 'float':
                return floatval($filterValue);
            case 'double':
                return doubleval($filterValue);
            case 'boolean':
                return boolval($filterValue);
            case 'string':
                return strval($filterValue);
            default:
                return $filterValue;
        }

    }

    /**
     * 判断是否存在某个通信参数值，第二参数用来校验参数值是否匹配
     *
     * @author liang<lang@vip.deyi.com>
     * @param $fieldName
     * @param string $equal
     * @return bool
     */
    public function hasParamValue($fieldName, $equal = '')
    {
        if (!isset($this->jsonReqArr[$fieldName])) {
            return false;
        }

        if ($equal && ($this->jsonReqArr[$fieldName] != $equal)) {
            return false;
        }

        return true;
    }


    /**
     *  参数过滤
     *
     * @author liang<lang@vip.deyi.com>
     *
     * @param $v
     *
     * @return array
     */
    private static function filterStr(&$v)
    {
        if (is_array($v)) {
            foreach ($v as $k => $a) {
                if (is_array($a)) {
                    $v[$k] = self::filterStr($v[$k]);
                } else {
                    if ($a === null) {
                        $v[$k] = '';
                    } else {
                        $v[$k] = strip_tags(trim($v[$k]), '<br>');
                    }
                }
            }
        }

        return $v;
    }

    /**
     * 转换数组里面的null为空字符串
     *
     * @param $v
     *
     * @return array
     *
     */
    private static function filterNullToStr($v)
    {
        if (is_array($v)) {
            foreach ($v as $k => $a) {
                if (is_array($a)) {
                    $v[$k] = self::filterNullToStr($v[$k]);
                } else {
                    if ($a === null) {
                        $v[$k] = '';
                    }
                    if (is_numeric($a)) {
                        $v[$k] = $a . '';
                    }
                }
            }
        }

        return $v;
    }

    /**
     *  json输出
     *
     * @author liang<lang@vip.deyi.com>
     * @param $array
     * @param bool $pretty
     * @return string
     */
    protected static function jsonEncode($array, $pretty = true)
    {
        return json_encode(self::filterNullToStr($array), $pretty ? (JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) : JSON_UNESCAPED_UNICODE);
    }

    /**
     * json底层输出
     *
     * @author liang<lang@vip.deyi.com>
     * @param $array
     * @param bool $pretty
     */
    protected static function echoJson($array, $pretty = true)
    {
        header('Content-Type:application/json;charset=UTF-8');
        header('CostTime:' . round(DateTimeFunction::getExecutionTime(), 3));

        $echo = self::jsonEncode($array, $pretty);

        if (!$echo) {
            static::$_logger->error('json decode fail: ' . serialize($array));
        }
        echo $echo;
    }

    /**
     *  json数据渲染
     *
     * @author liang<lang@vip.deyi.com>
     *
     * @param $state
     * @param $v
     * @param $msg
     */
    public static function renderJson($state, $v, $msg)
    {
        $array['code'] = $state;
        $array['data'] = $v;
        $array['msg'] = $msg;

        if ($state !== BusinessConstant::NORMAL_STATUS_CODE) {
            if (!in_array($state, ExceptionConstant::getConstants())) {
                $array['code'] = ExceptionConstant::INTERNAL_ERROR;
                $array['msg'] = '服务器君开小差去啦~';
            }
        }

        self::echoJson($array, false);
    }

    /**
     *  响应成功时Json数据组装
     *
     * @author liang<lang@vip.deyi.com>
     *
     * @param        $v
     * @param string $msg
     */
    public static function renderSuccessJson($v = [], $msg = 'ok')
    {
        //避免上层逻辑中事物没有正常提交
        BaseModel::commitTransaction();
        self::renderJson(BusinessConstant::NORMAL_STATUS_CODE, $v, $msg);
    }

    /**
     * 响应失败时Json数据组装
     * @author lang@vip.deyi.com
     * @param $errCode
     * @param $errMsg
     * @throws \Exception
     */
    public static function renderFailedJson($errCode, $errMsg)
    {
        self::errorThrow($errMsg, $errCode);
    }

    /**
     * 自动 添加 img url http链接 前缀
     *
     * @param $urlOrUri
     * @param $ossProcess //阿里云oss图片处理定义
     * @return string
     */
    protected function doAddImgUrlDomain(&$urlOrUri, $ossProcess = '')
    {

        if (is_array($urlOrUri)) {
            foreach ($urlOrUri as &$uri) {
                if ('' !== $uri && false == stristr($uri, 'https:') && false == stristr($uri, 'http:')) {
                    $uri = self::$_config->project->imageurl . $uri . $ossProcess;
                }
            }
        } else {
            if ('' !== $urlOrUri && false == stristr($urlOrUri, 'https:') && false == stristr($urlOrUri, 'http:')) {
                $urlOrUri = self::$_config->project->imageurl . $urlOrUri . $ossProcess;
            }

        }

        return $urlOrUri;
    }

    /**
     *  过滤掉图片地址host部分
     *
     * @param $uploadImgUrl
     * @return mixed
     */
    protected function doSubImgUrlDomain($uploadImgUrl)
    {
        return str_replace(self::$_config->project->imageurl, '', $uploadImgUrl);
    }

    /**
     *  抛出异常
     *
     * @param $msg
     * @param int $errorCode
     * @throws \Exception
     */
    protected static function errorThrow($msg, $errorCode = ExceptionConstant::INTERNAL_ERROR)
    {
        throw new \Exception($msg, $errorCode);
    }

    /**
     *  接口增加互斥锁，避免频繁请求
     *
     * @param string $key
     * @param int $sec
     * @param bool $throw
     * @return bool
     * @throws \Exception
     */
    public function mutexlock($key = '', $sec = 1, $throw = true)
    {
        self::$_mutex_lock_cache_key = CacheFunction::generateKey(CacheKeyConstant::MUTEX_LOCK_CACHE_KEY, $key);
        self::$_mutex_lock_timeout = $sec;
        if (!self::$_memcached->add(self::$_mutex_lock_cache_key, 1, $sec)) {
            if ($throw) {
                throw new \Exception('请勿频繁请求!', ExceptionConstant::MUTEX_LOCK_OCCUR);
            } else {
                self::$THROW_ERR_CODE = ExceptionConstant::MUTEX_LOCK_OCCUR;

                return false;
            }
        }

        return true;
    }

    /**
     *  互斥锁解锁调用方法
     *
     */
    public static function mutexUnlock()
    {
        if (!empty(self::$_mutex_lock_cache_key)) {
            if (self::$THROW_ERR_CODE === ExceptionConstant::MUTEX_LOCK_OCCUR) {
                self::$_memcached->delete(self::$_mutex_lock_cache_key, self::$_mutex_lock_timeout);
            } else {
                self::$_memcached->delete(self::$_mutex_lock_cache_key);
            }
        }
    }

    /**
     * 获取接口级数据缓存
     *
     * @param string $prefix
     * @param bool $useLocalCache
     * @return mixed
     */
    public function getUrlDataCache($prefix = '', $useLocalCache = false)
    {
        $cacheKey = CacheFunction::generateKey(
            CacheKeyConstant::URL_PREFIX_CACHE_KEY,
            $prefix,
            $this->request->getURI(),
            $this->request->getQuery(),
            $this->request->getRawBody()
        );
        return $useLocalCache ? apcu_fetch($cacheKey) : static::$_memcached->get($cacheKey);
    }

    /**
     * 对接口数据写入缓存
     *
     * @param $data
     * @param int $timeout
     * @param $prefix
     * @param bool $useLocalCache
     * @return bool
     */
    public function setUrlDataCache($data, $timeout = CacheTimeoutConstant::LOCAL_CACHE, $prefix = '', $useLocalCache = false)
    {
        $cacheKey = CacheFunction::generateKey(
            CacheKeyConstant::URL_PREFIX_CACHE_KEY,
            $prefix,
            $this->request->getURI(),
            $this->request->getQuery(),
            $this->request->getRawBody()
        );
        return $useLocalCache ? apcu_store($cacheKey, $data, $timeout) : static::$_memcached->set(
            $cacheKey,
            $data,
            $timeout
        );
    }

    /**
     *  重复请求安全检查函数（保证业务只有一次请求有效性）
     *
     * @param string $prefix
     * @param int $expire
     * @param bool $contains 包含额外的参数
     * @return bool
     */
    public function onlyOneRequestPassCheck($prefix = '', $expire = CacheTimeoutConstant::LOCAL_CACHE, $contains = true)
    {
        if (static::$_redis->set(
            $contains ? CacheFunction::generateKey(
                CacheKeyConstant::MUTEX_LOCK_CACHE_KEY,
                CacheKeyConstant::URL_PREFIX_CACHE_KEY,
                $this->request->getURI(),
                $this->request->getQuery(),
                $this->request->getRawBody(),
                $prefix
            ) : CacheFunction::generateKey(
                CacheKeyConstant::MUTEX_LOCK_CACHE_KEY,
                CacheKeyConstant::URL_PREFIX_CACHE_KEY,
                $this->request->getURI(),
                $prefix
            ),
            1,
            ['nx', 'ex' => $expire]
        )
        ) {
            return true;
        }

        return false;
    }

    /**
     * API请求限制检查
     * @param $prefix
     * @param $total
     * @param int $expire
     * @param string $msg
     * @param bool $throw
     * @return bool
     * @throws \Exception
     */
    public function requestRateLimitCheck($prefix, $total, $expire = CacheTimeoutConstant::LOCAL_CACHE, $msg = '请求频率过快!', $throw = true)
    {
        return ReqFreqControlService::getInstance()->rateLimitByLocalCache($this->request->getURI() . $prefix, $total, $expire, $msg, $throw);
    }

    /**
     * API请求限制检查(Redis数据落地)
     * @param $prefix
     * @param $total
     * @param int $expire
     * @param string $msg
     * @param bool $throw
     * @return bool
     * @throws \Exception
     */
    public function requestRateLimitCheckByRedis(
        $prefix,
        $total,
        $expire = CacheTimeoutConstant::LOCAL_CACHE,
        $msg = '请求频率过快!',
        $throw = true
    ) {
        return ReqFreqControlService::getInstance()->rateLimitByReids($this->request->getURI() . $prefix, $total, $expire, $msg, $throw);
    }

    /**
     * 获取当前请求完整地址url
     * @return string
     */
    public function getCurrentFullUrl()
    {
        $host = $this->request->getHttpHost();
        return $this->request->getScheme() . '://' . (strpos($host, ':') ? strstr($host, ':', true) : $host) . $this->request->getURI();
    }

    /**
     * 获取微信ticket授权
     * @return bool|mixed|string
     */
    public function getWeixinJsApiTicket()
    {
        $cacheKey = CacheFunction::generateKey(CacheKeyConstant::WEIXIN_JSAPI_TICKET_CACHE_KEY, date('H'));

        $res = static::$_redis->get($cacheKey);

        if ($res) {
            return $res;
        }

        $accessToken = $this->getWeixinAccessToken();

        if (!$accessToken) {
            return false;
        }

        $url = sprintf(
            "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=%s&type=jsapi",
            $accessToken
        );

        $res = HttpFunction::getPage($url, 3);
        if ($res && ($res = json_decode($res, true)) && isset($res['ticket'])) {
            $res = $res['ticket'];
        } else {
            return false;
        }

        static::$_redis->set($cacheKey, $res, CacheTimeoutConstant::LOCAL_HOUR_CACHE);

        return $res;
    }

    public function getWeixinAccessToken()
    {
        $cacheKey = CacheFunction::generateKey(CacheKeyConstant::WEIXIN_ACCESS_TOKEN_CACHE_KEY, date('H'));

        $res = static::$_redis->get($cacheKey);

        if ($res) {
            return $res;
        }

        $url = sprintf(
            "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s",
            static::$_config->weixin->appid,
            static::$_config->weixin->appsecret
        );
        $res = HttpFunction::getPage($url, 3);

        if ($res && ($res = json_decode($res, true)) && isset($res['access_token'])) {
            $res = $res['access_token'];
        } else {
            return false;
        }

        static::$_redis->set($cacheKey, $res, CacheTimeoutConstant::LOCAL_HOUR_CACHE);

        return $res;
    }
    /**
     * 获取微信jsapi 签名
     * @param $nonceStr
     * @param $timestamp
     * @param string $url
     * @return bool|string
     */
    public function getWeixinJsApiSign($nonceStr, $timestamp, $url = '')
    {
        $ticket = $this->getWeixinJsApiTicket();

        if (!$ticket) {
            return false;
        }

        $param = [];
        $param['noncestr'] = $nonceStr;
        $param['jsapi_ticket'] = $ticket;
        $param['timestamp'] = $timestamp;
        $param['url'] = $url ? $url : $this->getCurrentFullUrl();
        ksort($param);
        return sha1(rawurldecode(http_build_query($param, null, '&', PHP_QUERY_RFC3986)));
    }

    //更新缓存
    public function deleteCacheByKey($cacheKey)
    {
        $res = [];
        if (is_array($cacheKey)) {
            foreach ($cacheKey as $val) {
                $res[] = static::$_memcached->delete($val);
            }
        } else {
            $res[] = static::$_memcached->delete($cacheKey);
        }

        return $res;
    }

    /**
     * 强制重定向URL
     * @param $url
     * @param bool $isPermanently
     * @throws \Exception
     */
    public function redirectToUrl($url, $isPermanently = false)
    {
        // 解决cookies未随头部发送的问题
        if (!$this->response->isSent()) {
            $this->response->send();
        }
        header('Location: ' . $url, true, $isPermanently ? 301 : 302);
        static::errorThrow('', ExceptionConstant::REDIRECT_URL);
    }

    /**
     * 判断是否是移动设备
     * @return bool
     */
    public function isMobileDevice()
    {
        return MobileDeviceDetect::getInstance()->isMobile();
    }

    /**
     * URL参数加密
     * @param $str
     * @return string
     */
    public function encryptUrlParam($str)
    {
        return UtilFunction::encryptUrlParam($str);
    }

    /**
     * URL参数解密
     * @param $str
     * @return string
     */
    public function decryptUrlParam($str)
    {
        return UtilFunction::decryptUrlParam($str);
    }
}
